﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization;
using System.Windows.Forms;


namespace FAST
{
    [Serializable()]
    public class FileVersioning
    {
        private List<FileVersioningList> versionList;
        private int overLimitBackupRemoveBit;
        private int restoreBackupRemoveBit;
        private String deletedFilesBackupLocation;
        private String storageFilePath;
        private Boolean isDriveChange;
        
        //Method name : FileVersioning() : N.A for constructors.
        //Parameters  : None.
        //Description : An instance of the FileVersioning constructor which stores a string deletedFilesBackupLocation, a List<FileVersioningList> versionList, an int overLimitBackupRemoveBit and an int restoreBackupRemoveBit.
        //Exceptions  : None.
        public FileVersioning()
        {
            isDriveChange = false;
            storageFilePath = "";
            string path = Application.StartupPath;
            string diskLetter = path.Substring(0, path.IndexOf('\\') + 1);

            deletedFilesBackupLocation = diskLetter + @"Sessions\Backup\";//location used to store the deleted version files
            versionList = new List<FileVersioningList>(); //A list of list<FileHandler>

            //default set as 0 --> 0 for backup, 1 for remove -> for files over version limit
            overLimitBackupRemoveBit = 0; // backup the oldest file in another location or delete it permanently

            //default set as 0 --> 0 for backup, 1 for remove -> for reverting back versioned files
            restoreBackupRemoveBit = 0; // backup the original file by making a version out of it or delete it permanently

            Serializer ser = new Serializer();
            //check file exist
            if (!File.Exists(diskLetter + @"\Sessions\versionList.dat"))
            {
                versionList = new List<FileVersioningList>();
            }
            else
            {
                versionList = ser.deserializeFVList(diskLetter + @"\Sessions\versionList.dat");
            }

            string message = "";
            int count = 0;
            foreach (FileVersioningList test in versionList)
            {
                message += "index " + count + " ";
            }

        }

        //Method name : FileVersioning() : N.A for constructors.
        //Parameters  : rootPath(type String)
        //Description : An instance of the FileVersioning constructor which stores a string deletedFilesBackupLocation, a List<FileVersioningList> versionList, an int overLimitBackupRemoveBit and an int restoreBackupRemoveBit. This constructor is only for removable drives where rootPath is the removable drive's root path.
        //Exceptions  : None.
        public FileVersioning(String rootPath)
        {
            isDriveChange = true;
            storageFilePath = "";
            string path = rootPath;
            string diskLetter = path.Substring(0, path.IndexOf('\\') + 1);

            deletedFilesBackupLocation = diskLetter + @"Sessions\Backup\";//location used to store the deleted version files
            versionList = new List<FileVersioningList>(); //A list of list<FileHandler>

            //default set as 0 --> 0 for backup, 1 for remove -> for files over version limit
            overLimitBackupRemoveBit = 0; // backup the oldest file in another location or delete it permanently

            //default set as 0 --> 0 for backup, 1 for remove -> for reverting back versioned files
            restoreBackupRemoveBit = 0; // backup the original file by making a version out of it or delete it permanently

            Serializer ser = new Serializer();
            //check file exist
            if (!File.Exists(diskLetter + @"\Sessions\versionList.dat"))
            {
                versionList = new List<FileVersioningList>();
            }
            else
            {
                versionList = ser.deserializeFVList(diskLetter + @"\Sessions\versionList.dat");
            }

            string message = "";
            int count = 0;
            foreach (FileVersioningList test in versionList)
            {
                message += "index " + count + " ";
            }

        }

        //Method name : setStorageFilepath() : void
        //Parameters  : storageFP(type String)
        //Description : Sets the value of storageFilePath.
        //Exceptions  : None.
        public void setStorageFilePath(String storageFP)
        {
            this.storageFilePath = storageFP;
        }

        //Method name : getStorageFilepath() : void
        //Parameters  : None.
        //Description : Returns the value of storageFilePath.
        //Exceptions  : NullException.
        public String getStorageFilePath()
        {
            return this.storageFilePath;
        }

        //Method name : getListOfFVList() : List<FileVersioningList>
        //Parameters  : None.
        //Description : Returns the attribute 'versionList' as a List<FileVersioningList> item.
        //Exceptions  : None.
        public List<FileVersioningList> getListOfFVList()
        {
            return this.versionList;
        }
        
        //Method name : setOverLimitBackupRemoveBit() : void
        //Parameters  : bit(type int)
        //Description : Sets the value of overLimitBackupRemoveBit.
        //Exceptions  : OutOfRangeValue Exception: Can only set bit to 0 or 1.
        public void setOverLimitBackupRemoveBit(int bit)
        {
            this.overLimitBackupRemoveBit = bit;
        }
        
        //Method name : getOverLimitBackupRemoveBit() : int
        //Parameters  : None.
        //Description : Returns the value of overLimitBackupRemoveBit.
        //Exceptions  : None.
        public int getOverLimitBackupRemoveBit()
        {
            return this.overLimitBackupRemoveBit;
        }

        //Method name : setRestoreBackupRemoveBit() : void
        //Parameters  : bit(type int)
        //Description : Sets the value of restoreBackupRemoveBit.
        //Exceptions  : OutOfRangeValue Exception: Can only set bit to 0 or 1.
        public void setRestoreBackupRemoveBit(int bit)
        {
            this.restoreBackupRemoveBit = bit;
        }

        //Method name : getRestoreBackupRemoveBit() : int
        //Parameters  : None.
        //Description : Returns the value of restoreBackupRemoveBit.
        //Exceptions  : None.
        public int getRestoreBackupRemoveBit()
        {
            return this.restoreBackupRemoveBit;
        }

        //Method name : revertVersion() : void
        //Parameters  : file(type FileHandler), ver(type int)
        //Description : Reverts a file of version number 'ver' by replacing it with the current 'file'.
        //Exceptions  : InvalidVersions Exception: Invalid value of 'ver' when retrieveing the specified file.
        public void revertVersion(FileHandler file, int ver)
        {
            //serialize file versioning list
            Serializer ser = new Serializer();
            String path1 = this.getStorageFilePath();
            string diskLetter1 = path1.Substring(0, path1.IndexOf('\\') + 1);
            string temp = diskLetter1 + @"Sessions\versionList.dat";
            if (File.Exists(temp))
            {
                this.versionList = ser.deserializeFVList(temp);

            }

            int idx = file.getVersioningIdx();
            int currentRevertedIndex = this.getListOfFVList()[idx].getCurrentRevertedVersion();
            //MessageBox.Show("At start, when retrieving currentRevertedIndex == " + currentRevertedIndex);
            Helper hashSys = new Helper();
            String fileName1 = file.getFullFilePath();
            String f2 = file.getSubFilePath();
            Console.WriteLine(f2);
            int count = 0;

            if (this.getRestoreBackupRemoveBit() == 0) //backup a version of file before deletion
            {
                String fileToMove = this.replaceCurrentWithVersion(file, idx, ver);
                if (currentRevertedIndex != -1) //means is a version
                {
                    //Continue check if this file has been modified not
                    foreach (FileHandler fh in this.getListOfFVList()[idx].getListOfFH())
                    {
                        //MessageBox.Show(fh.getFullFilePath());
                        if (currentRevertedIndex == this.retrieveVersionNumber(fh.getFullFilePath()))
                        {
                            //MessageBox.Show("has a version, checking md5 now");
                            count = 1;//if we can find a version of this file
                            String hashCode = hashSys.computeHashCode(fileName1);
                            if (!hashCode.Equals(fh.getMd5HashCode()))//recheck its hash code to look for modifications
                            {
                                //MessageBox.Show("current file hash code " + hashCode + " not equal with that of its version: " + fh.getMd5HashCode());
                                this.createVersion(file);//if different, we version it
                                break;
                            }
                        }
                    }
                    if (count == 0)//else if we cant find a version of this file even though it is versioned before
                    {
                        //MessageBox.Show("does not have a version, creating one for it now");
                        this.createVersion(file);//we version it
                    }
                }
                else//else if not a version, we version it
                {
                    //MessageBox.Show("currentRevertedIndex == " + currentRevertedIndex);
                    this.createVersion(file);
                }
                //this.replaceCurrentWithVersion(file, idx, ver);
                //move : File.Move( "c:\\friends.jpg", "c:\\temp\\friends.jpg");
                File.Delete(file.getFullFilePath());
                File.Move(fileToMove, file.getFullFilePath());
                //this.getListOfFVList()[idx].setCurrentRevertedVersion(ver);
                //MessageBox.Show("after revert, new CRV:" + this.getListOfFVList()[idx].getCurrentRevertedVersion() + "");
            }
            else if (this.getRestoreBackupRemoveBit() == 1) //delete current file
            {
                String fileToMove = this.replaceCurrentWithVersion(file, idx, ver);
                //move
                File.Delete(file.getFullFilePath());
                File.Move(fileToMove, file.getFullFilePath());
                //this.getListOfFVList()[idx].setCurrentRevertedVersion(ver);
            }
            this.getListOfFVList()[idx].setCurrentRevertedVersion(ver);
            //MessageBox.Show("after revert, new CRV:" + this.getListOfFVList()[idx].getCurrentRevertedVersion() + "");

            //this.setStorageFilePath(file.getFullFilePath());
            String path = this.getStorageFilePath();
            string diskLetter = path.Substring(0, path.IndexOf('\\') + 1);
            ser.serializeFVList(diskLetter + @"Sessions\versionList.dat", versionList);
        }

        //Method name : replaceCurrentWithVersion() : void
        //Parameters  : file(type FileHandler), idx(type int), ver(type int), c(type int)
        //Description : Replaces the current file with its versioned file of version number 'ver'
        //Exceptions  : InvalidVersions Exception: Invalid value of 'ver' when retrieveing the specified file.
        public String replaceCurrentWithVersion(FileHandler file, int idx, int ver)
        {
            String fhFilePath = "";
            //File.Delete(file.getFullFilePath());
            foreach (FileHandler fh in this.getListOfFVList()[idx].getListOfFH())
            {
                if (ver == this.retrieveVersionNumber(fh.getFullFilePath()))
                {
                    //this.getListOfFVList()[idx].setCurrentRevertedVersion(ver);//OR//
                    //MessageBox.Show(""+this.getListOfFVList()[idx].getCurrentRevertedVersion()+"");
                    //move version file to current file location   
                    fhFilePath = fh.getFullFilePath();
                    FileInfo MyFile = new FileInfo(fhFilePath);
                    String fhFilePathFront = fhFilePath.Substring(0, fhFilePath.LastIndexOf("_v["));
                    String fhFilePathExt = fhFilePath.Substring(fhFilePath.LastIndexOf("]") + 1);
                    fhFilePath = "" + fhFilePathFront + fhFilePathExt + "";
                    Console.WriteLine("fhFilePath: " + fhFilePath);
                    MyFile.CopyTo(fhFilePath, true); //copy to same version folder temporarily
                }
            }
            return fhFilePath;
        }

        //Method name : createVersion() : void
        //Parameters  : file(type FileHandler)
        //Description : Creates a new version of the current 'file'
        //Exceptions  : FileNotFound Exception: File is invalid or cannot be found.
        public void createVersion(FileHandler file)
        {
            this.setStorageFilePath(file.getFullFilePath());
            //retrieve file versioning index from file
            int idx = file.getVersioningIdx();

            //check for existing versions of target file if earlier versions exist
            if (idx != -1)
            {   //check for version limit, if not yet reach limit, continue
                if (this.getListOfFVList()[idx].getListCount() < this.getListOfFVList()[idx].getVersionLimit())
                {
                    this.getListOfFVList()[idx].createVersionInList(file);
                }
                else //else remove oldest version and add a new one
                {
                    Console.WriteLine("File Version Limit reached. ");
                    String oldestFile = this.getOldestFileToBeRemoved(idx);
                    //Currently removes the versions in list by FCFS order
                    //such that the top of the list will always be removed i.e. list[0]
                    if (this.overLimitBackupRemoveBit == 1) //1 for remove oldest version
                    {
                        Console.WriteLine("Removing oldest file: [" + oldestFile + "]");
                        File.Delete(oldestFile);
                        this.removeCurrentSetNextOldAndCreateNextVer(idx, 0, file);//remove oldest file
                    }
                    else if (this.overLimitBackupRemoveBit == 0)//0 for backup oldest version
                    {
                        String fp = file.getFullFilePath();
                        String drive = fp.Substring(0, fp.LastIndexOf(@":\"));
                        drive = drive + "_drive"; //changes 'C:\' to 'c_drive' folder
                        String pathAfterRoot = fp.Substring(fp.IndexOf(@":\") + 1);
                        pathAfterRoot = pathAfterRoot.Substring(0, pathAfterRoot.LastIndexOf(@"\"));
                        String newParentRootFullFP = this.deletedFilesBackupLocation + drive + @"\" + pathAfterRoot + @"\";
                        if (!Directory.Exists(newParentRootFullFP))
                        {
                            Directory.CreateDirectory(newParentRootFullFP);
                        }
                        String oldestFile2 = newParentRootFullFP + oldestFile.Substring(oldestFile.LastIndexOf(@"\") + 1);
                        File.Move(oldestFile, oldestFile2);
                        this.removeCurrentSetNextOldAndCreateNextVer(idx, 0, file);//remove oldest file
                    }
                }
            }
            //else if no earlier versions exist, create a new list of versions for it
            else
            {
                if (this.isDriveChange) //sync with external
                {
                    FileVersioningList fvl = new FileVersioningList(this.getStorageFilePath());
                    this.getListOfFVList().Add(fvl);
                    fvl.setFullFP(file.fullFilePath);
                    this.getListOfFVList()[this.getListOfFVList().Count - 1].createVersionInList(file);
                    file.setVersioningIdx(this.getListOfFVList().Count - 1);
                }
                else //local sync
                {
                    FileVersioningList fvl = new FileVersioningList();
                    this.getListOfFVList().Add(fvl);
                    fvl.setFullFP(file.fullFilePath);
                    this.getListOfFVList()[this.getListOfFVList().Count - 1].createVersionInList(file);
                    file.setVersioningIdx(this.getListOfFVList().Count - 1);
                }
            }
           
            //serialize file versioning list
            Serializer ser = new Serializer();
            //this.setStorageFilePath(file.getFullFilePath());
            String path = this.getStorageFilePath();
            string diskLetter = path.Substring(0, path.IndexOf('\\') + 1);
            ser.serializeFVList(diskLetter + @"Sessions\versionList.dat", versionList);
        }
        
        //Method name : removeCurrentSetNextOldAndCreateNextVer() : void
        //Parameters  : idx(type int), pos(type int), file(type FileHandler)
        //Description : Removes the oldest versioned file, set oldestFileIdx to the next oldest file and create a next version with a new version number.
        //Exceptions  : FileNotFound Exception: File is invalid or cannot be found.
        public void removeCurrentSetNextOldAndCreateNextVer(int idx, int pos, FileHandler file)
        {
            this.setNextOldestVersion(idx);
            this.removeFromListOfFH(idx, pos);
            this.getListOfFVList()[idx].createVersionInList(file);
        }
        
        //Method name : getOldestFileToBeRemoved() : String
        //Parameters  : idx(type int)
        //Description : Returns the full path name of the oldest versioned file to be removed.
        //Exceptions  : FileNotFound Exception: File is invalid or cannot be found.
        public String getOldestFileToBeRemoved(int idx)
        {
            String fullfp = this.getListOfFVList()[idx].getFullFP();
            String filename = fullfp.Substring(0, fullfp.LastIndexOf("."));
            filename = filename.Substring(filename.LastIndexOf("\\") + 1);
            String ext = fullfp.Substring(fullfp.LastIndexOf(".") + 1);

            String drive = fullfp.Substring(0, fullfp.LastIndexOf(@":\"));
            drive = drive + "_drive"; //changes 'C:\' to 'c_drive' folder
            String pathAfterRoot = fullfp.Substring(fullfp.IndexOf(@":\") + 1);
            pathAfterRoot = pathAfterRoot.Substring(0, pathAfterRoot.LastIndexOf(@"\"));
            String newParentRootFullFP = this.getListOfFVList()[idx].getVersionedFilesLocation() + drive + @"\" + pathAfterRoot + @"\";
            if (!Directory.Exists(newParentRootFullFP))
            {
                Directory.CreateDirectory(newParentRootFullFP);
            }
            String fp = newParentRootFullFP + filename + "_v[" + this.getListOfFVList()[idx].getOldestVersion() + "]." + ext;

            return fp;
        }

        //Method name : setNextOldestVersion() : void
        //Parameters  : idx(type int)
        //Description : Sets the next file to be the oldest version after removing the current oldest version.
        //Exceptions  : FileNotFound Exception: File is invalid or cannot be found.
        public void setNextOldestVersion(int idx)
        {
            this.getListOfFVList()[idx].setOldestVersion(this.getListOfFVList()[idx].getOldestVersion() + 1);
        }

        //Method name : getListCount() : int
        //Parameters  : None.
        //Description : Returns the number of items in the list.
        //Exceptions  : None.
        public int getListCount()
        {//returns the number of FileVersioningList objects in list
            return this.getListOfFVList().Count;
        }

        //Method name : retrieveVersionNumber() : int
        //Parameters  : fp(type String)
        //Description : Returns the version number of the current file given its full file path.
        //Exceptions  : FileNotFound Exception: File is invalid or cannot be found.
        public int retrieveVersionNumber(String fp)
        {//returns the file version value
            String temp = fp.Substring(0, fp.LastIndexOf("]"));
            temp = temp.Substring(fp.LastIndexOf("[") + 1);
            return System.Convert.ToInt32(temp);
        }

        //Method name : removeFromListOfFH() : void
        //Parameters  : idx(type int), positionInList(type int)
        //Description : Removes current file of index 'positionInList' and 'idx' in List<FileVersioningList>
        //Exceptions  : ArrayOutOfBounds Exception: Invalid value of range given.
        public void removeFromListOfFH(int idx, int positionInList)
        {//removes FileHandler object from FileVersioningList of index 'idx' and at position 'positionInList' that list
            this.getListOfFVList()[idx].getListOfFH().Remove(this.getListOfFVList()[idx].getListOfFH()[positionInList]);
        }

        //Method name : setVersionListByIdx() : void
        //Parameters  : vList(type List<FileHandler>), idx(type int)
        //Description : For UI to store comments of a particular file
        //Exceptions  : None.
        public void setVersionListByIdx(List<FileHandler> vList, int idx)
        {
            if (idx != -1)
            {
                string path = Application.StartupPath;
                string diskLetter = path.Substring(0, path.IndexOf('\\') + 1);

                this.versionList[idx].setVersionList(vList);
                Serializer ser = new Serializer();
                ser.serializeFVList(diskLetter + @"Sessions\versionList.dat", versionList);
            }

        }
        
    }
}
